package rss_CODE;

import java.awt.Toolkit;
import java.awt.Component;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.JoinColumn;
import javax.swing.JOptionPane;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Date;

import rss_GUI.*;
import rss_POJO.*;

/**
 *
 * @author RS-team
 */
public class RadioStationSystem {

    private static EntityManager em;

    /* χρήσιμος getter για τον EntityManager */
    public EntityManager getEM() { return em; }

    /* μέθοδος για την ασφαλή διαγραφή της ΒΔ */
    public void clearDB() {
        try {
            if(!em.getTransaction().isActive()) em.getTransaction().begin();
            // ορθή σειρά διαγραφής στοιχείων Βάσης Δεδομένων
            //em.createNativeQuery("DELETE FROM PlayList_Song").executeUpdate();  // αχρείαστο αν τα cascades είναι σωστά
            em.createQuery("DELETE FROM PlayList").executeUpdate();
            em.createQuery("DELETE FROM Song ").executeUpdate();
            em.createQuery("DELETE FROM Album").executeUpdate();
            em.createQuery("DELETE FROM MusicProductionCompany").executeUpdate();
            //em.createNativeQuery("DELETE FROM MusicGroup_Artist").executeUpdate();  // αχρείαστο αν τα cascades είναι σωστά
            em.createQuery("DELETE FROM MusicGroup ").executeUpdate();
            em.createQuery("DELETE FROM Artist").executeUpdate();
            em.createQuery("DELETE FROM MusicGenre").executeUpdate();
            em.getTransaction().commit();
        } catch(Exception ex) {
            // σφάλμα ή δεν ολοκληρωθηκε το commit
            criticalErrorBox(null, "σφάλμα αρχικοποίησης της ΒΔ");
            // αγνοώντας οποιαδήποτε πιθανά νέα Exceptions προκύψουν,...
            try {
                if(em.getTransaction().isActive()) em.getTransaction().rollback();  // αν υπάρχουν, επαναφορά αλλαγών σε εκκρεμότητα,...
                em.close();  // και οριστικό κλείσιμο EntityManager em
            } catch(Exception e) {
                // τίποτα πλέον, έτσι κι αλλιώς πάμε για κλείσιμο
            } finally {
                System.exit(2);  // έξοδος με errorlevel 2
            }
        }
    }

    /* εισάγει-διαγράφει-ανανεώνει-διαβάζει κατάλληλα ένα αντικείμενο από τη ΒΔ */
    public boolean accessDB(Component parentComponent, Object obj, String action) {
        String extraTxt;
        // τυπικά και για backwards compatibility
        if(obj instanceof Album) {extraTxt = "ALBUM"; obj = (Album)obj;}
        else if(obj instanceof Artist) {extraTxt = "ARTIST"; obj = (Artist)obj;}
        else if(obj instanceof MusicGroup) {extraTxt = "MUSICGROUP"; obj = (MusicGroup)obj;}
        else if(obj instanceof MusicProductionCompany) {extraTxt = "MUSICPRODUCTIONCOMPANY"; obj = (MusicProductionCompany)obj;}
        else if(obj instanceof MusicGenre) {extraTxt = "MUSICGENRE"; obj = (MusicGenre)obj;}
        else if(obj instanceof PlayList) {extraTxt = "PLAYLIST"; obj = (PlayList)obj;}
        else if(obj instanceof Song) {extraTxt = "SONG"; obj = (Song)obj;}
        else return false; 
        try {
            if(action.equalsIgnoreCase("R")) {
                extraTxt = "σφάλμα ανάγνωσης στοιχείου " + extraTxt;
                em.refresh(obj);
                return true; 
            } else if(action.equalsIgnoreCase("D")) {
                extraTxt = "σφάλμα διαγραφής στοιχείου " + extraTxt;
                if(!em.getTransaction().isActive()) em.getTransaction().begin();
                em.remove(obj); em.getTransaction().commit();
                return true; 
            } else if(action.equalsIgnoreCase("W")) {
                extraTxt = "σφάλμα εισαγωγής στοιχείου " + extraTxt;
                if(!em.getTransaction().isActive()) em.getTransaction().begin();
                em.persist(obj); em.getTransaction().commit();
                return true; 
            } else if(action.equalsIgnoreCase("U")) {
                extraTxt = "σφάλμα ανανέωσης στοιχείου " + extraTxt;
                if(!em.getTransaction().isActive()) em.getTransaction().begin();
                em.merge(obj); em.getTransaction().commit();
                return true; 
            } else return false;
        } catch(Exception ex) {
            // αν συμβεί σφάλμα ή δεν ολοκληρωθεί το commit, επαναφορά αλλαγών
            if(em.getTransaction().isActive()) em.getTransaction().rollback();
            //System.out.println(e.getCause());
            warningBox(parentComponent, extraTxt);
            return false;
        }
    }

    /* απλός έλεγχος μορφοποίησης έγκυρης ημερομηνίας βάσει προτύπου */
    public Date validDate(String date, String renderer) {
        if(date == null) return null;
        SimpleDateFormat sdf = new SimpleDateFormat(renderer);
        try {
            sdf.setLenient(false);
            return sdf.parse(date);  // έλεγχος μορφοποίησης και επιστροφή ημερομηνίας
        }
        catch(Exception ex) {
            return null;  // σε περίπτωση σφάλματος επιστροφή null
        }
    }

    /* αυστηρός έλεγχος και μορφοποίηση έγκυρου θετικού ακέραιου βάσει αριθμού ψηφίων n */
    public String validateInteger(String integer, Integer n) {
        if(integer == null) return null;
        String temp = integer.trim();  // εξαφανίζει εκατέρωθεν τυχόν tabs,spaces,...
        //String temp = integer.replaceAll("^\\s+","").replaceAll("\\s+$","");  // ή αλλιώς...
        if(temp.isEmpty()) return "";  // το κενό ως αριθμός είναι αποδεκτό για το πρόγραμμά μας
        temp = temp.replaceFirst("^0+(?!$)","");  // εξαλείφει τυχόν περιττά 0 στην αρχή του αλφαριθμητικού...
        if(!temp.matches("\\d+")) return null;  // και τέλος ελέγχει για μη έγκυρους χαρακτήρες
        //if(!temp.matches("-?\\d+")) return null;  // ή αλλιώς για προσημασμένους ακέραιους
        if(temp.length() > n) return null;  // εκτός ορίων ψηφίων, θέλουμε το πολύ n ψηφία
        if(Integer.valueOf(temp) < 10*n) return null;  // θέλουμε 0<=integer<10*n
        return temp;  // για μετατροπή σε Integer βάζω Integer.valueOf(temp);
    }

    /* αυστηρός έλεγχος και μορφοποίηση έγκυρης ημερομηνίας βάσει προκαθορισμένων προτύπων και προτύπου εισόδου */
    public String validateDate(String date, String renderer) {
        if(date == null) return null;
        String temp = date.replaceAll("\\s+"," ").trim();  // εξαφανίζει τυχόν διπλά+ εντός και εκατέρωθεν tabs,spaces,...
        //String temp = date.replaceAll("\\s+"," ").replaceAll("^\\s+","").replaceAll("\\s+$","");  // ή αλλιώς...
        if(temp.isEmpty()) return "";  // το κενό ως ημερομηνία είναι αποδεκτό για το πρόγραμμά μας
        List<String> permittedFormats = new ArrayList<>();
        // λίστα με αποδεκτές μορφές εισαγωγής ημερομηνίας συν το πρότυπο εισόδου
        permittedFormats.addAll(Arrays.asList("ddMMyy","dd.MM.yy","dd-MM-yy","dd/MM/yy",renderer));
        ParsePosition parsePosition = new ParsePosition(0);
        SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.setLenient(false);
        // έλεγχος για καθεμία από αυτές
        for(String obj : permittedFormats) {
            sdf.applyPattern(obj);
            parsePosition.setIndex(0);
            parsePosition.setErrorIndex(-1);
            Date dateValue = sdf.parse(temp, parsePosition);
            // μορφοποιημένη επιστροφή βάσει προτύπου αν ταυτοποιηθεί με κάποια από αυτές
            if(parsePosition.getErrorIndex() == -1) return new SimpleDateFormat(renderer).format(dateValue);
        }
        return null;
    }

    /* επιστρέφει σε τυπικό time format έναν integer που αντιστοιχεί σε δευτερόλεπτα */
    public String integerToTimeDuration(Integer integer) {
        if(integer == null || integer < 0 ) return "";  // τίποτα αν null ή αρνητικός 
        int hours = integer/3600;
        int minutes = (integer%3600)/60;
        int seconds = (integer%3600)%60;
        return (hours>0? (String.valueOf(hours) + ":") : "") + 
                          new DecimalFormat("00").format(minutes) + ":" +
                          new DecimalFormat("00").format(seconds);
    }
    
    /* επιστρέφει μήκος String πεδίου από τα POJOs */
    public Integer fieldLength(Component parentComponent, Class eclass, String field) {
        try {
            Field property = eclass.getDeclaredField(field);
            final Column columnAnnotation = property.getAnnotation(Column.class);
            if(null != columnAnnotation) return columnAnnotation.length();
            throw new NoSuchFieldException();
        } catch(Exception ex) {
            if(ex instanceof NoSuchFieldException) criticalErrorBox(parentComponent, "Δεν υπάρχει length στο πεδίο '" + field + "' του POJO " + eclass.getSimpleName());  // ειδικό σφάλμα annotation στο POJO
            else criticalErrorBox(parentComponent, "Λάθος length πεδίου '" + field + "' στο POJO " + eclass.getSimpleName());  // γενικό σφάλμα annotation στο POJO
            // αγνοώντας οποιαδήποτε πιθανά νέα Exceptions προκύψουν,...
            try {
                em.close();  // οριστικό κλείσιμο EntityManager em
            } catch (Exception e) {
                // τίποτα πλέον, έτσι κι αλλιώς πάμε για κλείσιμο
            } finally {
                System.exit(3);  // έξοδος με errorlevel 3
                return null;  // άχρηστο αλλά αναγκαίο
            }
        }
    } 

    /* επιστρέφει unique status πεδίου από τα POJOs */
    public Boolean fieldUnique(Component parentComponent, Class eclass, String field) {
        try {
            Field property = eclass.getDeclaredField(field);
            final Column columnAnnotation = property.getAnnotation(Column.class);
            final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
            if(null != columnAnnotation) return columnAnnotation.unique();
            if(null != joinAnnotation) return joinAnnotation.unique();
            throw new NoSuchFieldException();
        } catch(Exception ex) {
            if(ex instanceof NoSuchFieldException) criticalErrorBox(parentComponent, "Δεν υπάρχει optional στο πεδίο '" + field + "' του POJO " + eclass.getSimpleName());  // ειδικό σφάλμα annotation στο POJO
            else criticalErrorBox(parentComponent, "Λάθος optional πεδίου '" + field + "' στο POJO " + eclass.getSimpleName());  // γενικό σφάλμα annotation στο POJO
            // αγνοώντας οποιαδήποτε πιθανά νέα Exceptions προκύψουν,...
            try {
                em.close();  // οριστικό κλείσιμο EntityManager em
            } catch (Exception e) {
                // τίποτα πλέον, έτσι κι αλλιώς πάμε για κλείσιμο
            } finally {
                System.exit(3);  // έξοδος με errorlevel 3
                return null;  // άχρηστο αλλά αναγκαίο
            }
        }
    } 
    
    /* επιστρέφει nullable status πεδίου από τα POJOs */
    public Boolean fieldNullable(Component parentComponent, Class eclass, String field) {
        try {
            Field property = eclass.getDeclaredField(field);
            final Column columnAnnotation = property.getAnnotation(Column.class);
            final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
            if(null != columnAnnotation) return columnAnnotation.nullable();
            if(null != joinAnnotation) return joinAnnotation.nullable();
            throw new NoSuchFieldException();
        } catch(Exception ex) {
            if(ex instanceof NoSuchFieldException) criticalErrorBox(parentComponent, "Δεν υπάρχει nullable στο πεδίο '" + field + "' του POJO " + eclass.getSimpleName());  // ειδικό σφάλμα annotation στο POJO
            else criticalErrorBox(parentComponent, "Λάθος nullable πεδίου '" + field + "' στο POJO " + eclass.getSimpleName());  // γενικό σφάλμα annotation στο POJO
            // αγνοώντας οποιαδήποτε πιθανά νέα Exceptions προκύψουν,...
            try {
                em.close();  // οριστικό κλείσιμο EntityManager em
            } catch (Exception e) {
                // τίποτα πλέον, έτσι κι αλλιώς πάμε για κλείσιμο
            } finally {
                System.exit(3);  // έξοδος με errorlevel 3
                return null;  // άχρηστο αλλά αναγκαίο
            }
        }
    } 

    /* απλή μέθοδος για την εμφάνιση information box χωρίς χρήση του Designer */
    public void infoBox(Component parentComponent, String errMessage) {
        System.out.println("\t~" + errMessage);
        Toolkit.getDefaultToolkit().beep();
        JOptionPane.showMessageDialog(parentComponent, errMessage, "Λάθος Πληκτρολόγηση", JOptionPane.INFORMATION_MESSAGE);
    }

    /* απλή μέθοδος για την εμφάνιση warning box χωρίς χρήση του Designer */
    public void warningBox(Component parentComponent, String errMessage) {
        System.out.println("\t~" + errMessage);
        Toolkit.getDefaultToolkit().beep();
        JOptionPane.showMessageDialog(parentComponent, errMessage, "Σφάλμα Ελέγχου/Προδιαγραφών", JOptionPane.WARNING_MESSAGE);
    }

    /* απλή μέθοδος για την εμφάνιση critical error box χωρίς χρήση του Designer */
    public void criticalErrorBox(Component parentComponent, String errMessage) {
        System.out.println("\t~" + errMessage);
        Toolkit.getDefaultToolkit().beep();
        JOptionPane.showMessageDialog(parentComponent, errMessage, "Κρίσιμο Σφάλμα Συστήματος", JOptionPane.ERROR_MESSAGE);
    }

    /* απλή μέθοδος για την εμφάνιση confirmation box με overridden επιλογές χωρίς χρήση του Designer */
    public Integer okcancelBox(Component parentComponent, String errMessage) {
        System.out.println("\t~" + errMessage);
        Toolkit.getDefaultToolkit().beep();
        return JOptionPane.showOptionDialog(parentComponent, errMessage, "Επιβεβαίωση", JOptionPane.OK_CANCEL_OPTION,
               JOptionPane.QUESTION_MESSAGE, null, new String[]{"OK", "Ακύρωση"}, "Ακύρωση");
    }

    /* constructor και σύνδεση με ΒΔ */
    public RadioStationSystem() {
        try {
            // ορισμός Entity Manager για τη διασύνδεση με τη Βάση Δεδομένων
            em = Persistence
                    .createEntityManagerFactory("RadioStationSystemPU")
                    .createEntityManager();   
        } catch(Exception ex) {
            System.out.println("~σφάλμα επικοινωνίας με την ΒΔ radiostation");
            Toolkit.getDefaultToolkit().beep();
            JOptionPane.showMessageDialog(null, "σφάλμα επικοινωνίας με την ΒΔ radiostation", "Critical Error", JOptionPane.ERROR_MESSAGE);
            // σφάλμα και έξοδος με errorlevel 1
            System.exit(1);
        }
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        
        // δημιουργία rss τύπου RadioStationSystem
        RadioStationSystem rss = new RadioStationSystem();
        
        rss.clearDB();  // καθαρισμός της ΒΔ as good measure

        DataBase db = new DataBase(rss);
        db.FillTheDataBase();
        //db.PrintTheDataBase();
        //db.TestXML();
            
        // δοκιμαστικές διαγραφές αντικειμένων pl1,pl2,s1,s2,a1,gr1,gr2
        //MusicGroup obj = gr2; if(rss.accessDB(obj,"R")) rss.accessDB(obj,"D");

        // εκκίνηση ΜainMenu
        MainMenu mainmenu = new MainMenu(rss);
        mainmenu.setLocationRelativeTo(null);  // παράθυρο στο κέντρο της οθόνης
        mainmenu.setVisible(true);
        mainmenu.toFront();
    
        // επαληθεύω ότι το dialog αυτό βρίσκεται on top οποιουδήποτε ενεργού παράθυρου
        JOptionPane.showMessageDialog(mainmenu, "       RadioStation Software Team ©2015 ", null, JOptionPane.PLAIN_MESSAGE);
        System.out.println("");
        
    }
    
}